ffmpeg 封装格式转换 MP4转AVI

格式转换直接将视音频压缩码流从一种封装格式文件中获取出来然后打包成另外一种封装格式的文件。因为不需要进行视音频的编码和解码,所以不会有视音频的压缩损伤。

主要步骤如下:

  1. 注册初始化
1
av_register_all();
  1. 打开输入文件,获取视频相关信息
1
2
avformat_open_input()
avformat_find_stream_info()
  1. 初始化输出文件的AVFormatContext并copy输入文件AVFormatContext的相关设置,打开输出文件
1
2
3
4
5
6
7
//为输出格式分配AVFormatContext
avformat_alloc_output_context2()
//【关键步骤】 copy输入文件的设置到输出文件
avcodec_copy_context()

//打开输出文件
avio_open()
  1. 写入数据到文件
1
2
3
4
5
6
7
8
9
//将文件流头部数据写入文件
avformat_write_header()
//读取帧
av_read_frame()

//数据包写入文件
av_interleaved_write_frame()
//将文件流尾部数据写入文件并释放文件资源
av_write_trailer()
  1. 回收

具体代码和步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

#include <stdio.h>
#include "stdafx.h"


extern "C"
{
#include <libavformat/avformat.h>
};


void log_ss(const char * msg, int d = -1123) {
if (d == -1123) {
printf_s("%s\n", msg);
}
else {
printf_s("%s %d \n", msg, d);
}
}

int main()
{
AVOutputFormat *ofmt = NULL;
//Input AVFormatContext and Output AVFormatContext
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
int ret, i;
int frame_index=0;
const char * in_filename = "F:/视频资源/gxsp.mp4";//Input file URL
const char * out_filename = "F:/视频资源/gxsp.avi";//Output file URL


//1. 注册初始化
//H.264 bitstream malformed, no startcode found, use the video bitstream filte错误解决方法 第一步
AVBitStreamFilterContext *filter = av_bitstream_filter_init("h264_mp4toannexb");
if (NULL == filter)
{
log_ss("filter init fail");
}
//av_register_bitstream_filter(filter->filter);
av_register_all();
//2. 打开媒体文件与AVFormatContext关联
//Input
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
log_ss( "Could not open input file.");
goto end;
}
//3. 获取视频流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {//
log_ss( "Failed to retrieve input stream information");
goto end;
}
log_ss("--------------- In File Information ----------------");
av_dump_format(ifmt_ctx, 0, in_filename, 0);
log_ss("--------------- In File Information ----------------");
//4. 初始化输出视频码流的AVFormatContext,与输出文件相关联
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx) {
log_ss( "Could not create output context");
ret = AVERROR_UNKNOWN;
goto end;
}

//获取AVOutputFormat
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
//Create output AVStream according to input AVStream
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);// 初始化AVStream
if (!out_stream) {
log_ss( "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
//5.【关键步骤】 copy输入文件的设置到输出文件
if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
log_ss( "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
log_ss("--------------- Out File Information ----------------");
av_dump_format(ofmt_ctx, 0, out_filename, 1);
log_ss("--------------- Out File Information ----------------");
//6. 打开输出文件。
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
log_ss( "Could not open output file");
log_ss( out_filename);
goto end;
}
}
//7. 写入头部到文件
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
log_ss( "Error occurred when opening output file\n");
goto end;
}

//8. 写入数据到文件
while (1) {
AVStream *in_stream, *out_stream;
//Get an AVPacket
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];

//H.264 bitstream malformed, no startcode found, use the video bitstream filte错误解决方法 第二步
if (pkt.stream_index == 0) {
AVPacket fpkt = pkt;
int a = av_bitstream_filter_filter(filter,
out_stream->codec, NULL, &fpkt.data, &fpkt.size,
pkt.data, pkt.size, pkt.flags & AV_PKT_FLAG_KEY);
pkt.data = fpkt.data;
pkt.size = fpkt.size;
}

//Convert PTS/DTS
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
//将AVPacket(存储视频压缩码流数据)写入文件 av_interleaved_write_frame将对 packet 进行缓存和 pts 检查,av_write_frame没有
if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {
log_ss( "Error muxing packets");
break;
}
log_ss("Write frames to output file:",frame_index);
av_packet_unref(&pkt);
frame_index++;
}

//9. 写入文件尾部
//Write file trailer
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);

getchar();
return 0;
}

遇到的错误:

  1. 错误C4996 ‘xxxx’: 被申明已否决
    原因: 因为api已经过时
    解决: 建议换用新的api,或者在属性->c/c++->常规里面关闭SDL检查

  2. H.264 bitstream malformed, no startcode found, use the video bitstream filte
    原因:主要是因为使用了mp4中的h264编码,而h264有两种封装:
    一种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中;另一种是mp4模式,一般mp4、mkv、avi会没有startcode,SPS和PPS以及其它信息被封装在container中,每一个frame前面是这个frame的长度,很多解码器只支持annexb这种模式,因此需要将mp4做转换;在ffmpeg中用h264_mp4toannexb_filter可以做转换;所以需要使用-bsf h264_mp4toannexb来进行转换;
    解决:
    (1) 注册过滤器

1
2
3
4
5
6
AVBitStreamFilterContext *filter = av_bitstream_filter_init("h264_mp4toannexb");
if (NULL == filter)
{
cout<<"filter init fail"<<endl;
}
av_register_bitstream_filter(filter->filter);

(2) 在获取第一帧的时候进行处理

1
2
3
4
5
6
7
8
if (pkt.stream_index == 0) {
AVPacket fpkt = pkt;
int a = av_bitstream_filter_filter(filter,
out_stream->codec, NULL, &fpkt.data, &fpkt.size,
pkt.data, pkt.size, pkt.flags & AV_PKT_FLAG_KEY);
pkt.data = fpkt.data;
pkt.size = fpkt.size;
}

参考:
ffmpeg实战教程(四)格式转换如MP4转MKV等
H.264 bitstream malformed, no startcode found, use the video bitstream filte错误解决方法

-------------The End-------------